 /*******************************************************************************
  * Copyright (c) 2000, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.ui.internal;

 import java.util.ArrayList ;
 import java.util.Arrays ;
 import java.util.Collection ;
 import java.util.Collections ;
 import java.util.HashMap ;
 import java.util.HashSet ;
 import java.util.Hashtable ;
 import java.util.Iterator ;
 import java.util.List ;
 import java.util.Map ;
 import java.util.Set ;

 import org.eclipse.core.runtime.IAdaptable;
 import org.eclipse.core.runtime.IAdapterManager;
 import org.eclipse.core.runtime.IConfigurationElement;
 import org.eclipse.core.runtime.IExtension;
 import org.eclipse.core.runtime.Platform;
 import org.eclipse.core.runtime.dynamichelpers.IExtensionChangeHandler;
 import org.eclipse.core.runtime.dynamichelpers.IExtensionTracker;
 import org.eclipse.jface.viewers.IStructuredSelection;
 import org.eclipse.ui.PlatformUI;
 import org.eclipse.ui.internal.util.Util;

 /**
  * This class is a default implementation of <code>IObjectContributorManager</code>.
  * It provides fast merging of contributions with the following semantics:
  * <ul>
  * <li> All of the matching contributors will be invoked per property lookup
  * <li> The search order from a class with the definition<br>
  * <code>class X extends Y implements A, B</code><br>
  * is as follows:
  * <il>
  * <li>the target's class: X
  * <li>X's superclasses in order to <code>Object</code>
  * <li>a depth-first traversal of the target class's interaces in the order
  * returned by <code>getInterfaces()</code> (in the example, A and
  * its superinterfaces then B and its superinterfaces)
  * </il>
  * </ul>
  *
  * @see IObjectContributor
  */
 public abstract class ObjectContributorManager implements IExtensionChangeHandler {
     
     /**
      * @since 3.1
      */
     private class ContributorRecord {
         /**
          * @param contributor
          * @param targetType
          */
         public ContributorRecord(IObjectContributor contributor, String targetType) {
             this.contributor = contributor;
             this.objectClassName = targetType;
         }
         
         String objectClassName;
         IObjectContributor contributor;
     }

     /** Table of contributors. */
     protected Map contributors;

     /** Cache of object class contributor search paths; <code>null</code> if none. */
     protected Map objectLookup;

     /** Cache of resource adapter class contributor search paths; <code>null</code> if none. */
     protected Map resourceAdapterLookup;
     
     /** Cache of adaptable class contributor search paths; <code>null</code> if none. */
     protected Map adaptableLookup;
     
     protected Set contributorRecordSet;

     /**
      * Constructs a new contributor manager.
      */
     public ObjectContributorManager() {
         contributors = new Hashtable (5);
         contributorRecordSet = new HashSet (5);
         objectLookup = null;
         resourceAdapterLookup = null;
         adaptableLookup = null;
         if(canHandleExtensionTracking()){
             IExtensionTracker tracker = PlatformUI.getWorkbench().getExtensionTracker();
             tracker.registerHandler(this, null);
         }
     }

     /**
      * Return whether or not the receiver handles extension
      * tracking. Default is <code>true</code>. Subclasses may override.
      * @return boolean <code>true</code> if it should be registered
      * as an extension handler.
      */
     protected boolean canHandleExtensionTracking() {
         return true;
     }

     /**
      * Adds contributors for the given types to the result list.
      */
     private void addContributorsFor(List types, List result) {
         for (Iterator classes = types.iterator(); classes.hasNext();) {
             Class clazz = (Class ) classes.next();
             List contributorList = (List ) contributors.get(clazz.getName());
             if (contributorList != null) {
                 result.addAll(contributorList);
             }
         }
     }

     /**
      * Returns the class search order starting with <code>extensibleClass</code>.
      * The search order is defined in this class' comment.
      */
     protected final List computeClassOrder(Class extensibleClass) {
         ArrayList result = new ArrayList (4);
         Class clazz = extensibleClass;
         while (clazz != null) {
             result.add(clazz);
             clazz = clazz.getSuperclass();
         }
         return result;
     }

     /**
      * Returns the interface search order for the class hierarchy described
      * by <code>classList</code>.
      * The search order is defined in this class' comment.
      */
     protected final List computeInterfaceOrder(List classList) {
         ArrayList result = new ArrayList (4);
         Map seen = new HashMap (4);
         for (Iterator list = classList.iterator(); list.hasNext();) {
             Class [] interfaces = ((Class ) list.next()).getInterfaces();
             internalComputeInterfaceOrder(interfaces, result, seen);
         }
         return result;
     }

     /**
      * Flushes the cache of contributor search paths. This is generally required
      * whenever a contributor is added or removed.
      * <p>
      * It is likely easier to just toss the whole cache rather than trying to be
      * smart and remove only those entries affected.
      */
     public void flushLookup() {
         objectLookup = null;
         resourceAdapterLookup = null;
         adaptableLookup = null;
     }

     /**
      * Cache the real adapter class contributor search path.
      */
     private void cacheResourceAdapterLookup(Class adapterClass, List results) {
         if (resourceAdapterLookup == null) {
             resourceAdapterLookup = new HashMap ();
         }
         resourceAdapterLookup.put(adapterClass, results);
     }
     
     /**
      * Cache the real adapter class contributor search path.
      */
     private void cacheAdaptableLookup(String adapterClass, List results) {
         if (adaptableLookup == null) {
             adaptableLookup = new HashMap ();
         }
         adaptableLookup.put(adapterClass, results);
     }

     /**
      * Cache the object class contributor search path.
      */
     private void cacheObjectLookup(Class objectClass, List results) {
         if (objectLookup == null) {
             objectLookup = new HashMap ();
         }
         objectLookup.put(objectClass, results);
     }

     /**
      * Get the contributions registered to this manager.
      *
      * @return an unmodifiable <code>Collection</code> containing all registered
      * contributions. The objects in this <code>Collection</code> will be
      * <code>List</code>s containing the actual contributions.
      * @since 3.0
      */
     public Collection getContributors() {
         return Collections.unmodifiableCollection(contributors.values());
     }

     /**
      * Return the list of contributors for the supplied class.
      */
     protected List addContributorsFor(Class objectClass) {

         List classList = computeClassOrder(objectClass);
         List result = new ArrayList ();
         addContributorsFor(classList, result);
         classList = computeInterfaceOrder(classList); // interfaces
 addContributorsFor(classList, result);
         return result;
     }

     /**
      * Returns true if contributors exist in the manager for
      * this object and any of it's super classes, interfaces, or
      * adapters.
      *
      * @param object the object to test
      * @return whether the object has contributors
      */
     public boolean hasContributorsFor(Object object) {

         List contributors = getContributors(object);
         return contributors.size() > 0;
     }

     /**
      * Add interface Class objects to the result list based
      * on the class hierarchy. Interfaces will be searched
      * based on their position in the result list.
      */
     private void internalComputeInterfaceOrder(Class [] interfaces, List result,
             Map seen) {
         List newInterfaces = new ArrayList (seen.size());
         for (int i = 0; i < interfaces.length; i++) {
             Class interfac = interfaces[i];
             if (seen.get(interfac) == null) {
                 result.add(interfac);
                 seen.put(interfac, interfac);
                 newInterfaces.add(interfac);
             }
         }
         for (Iterator newList = newInterfaces.iterator(); newList.hasNext();) {
             internalComputeInterfaceOrder(((Class ) newList.next())
                     .getInterfaces(), result, seen);
         }
     }

     /**
      * Return whether the given contributor is applicable to all elements in the
      * selection.
      *
      * @param selection
      * the selection
      * @param contributor
      * the contributor
      * @return whether it is applicable
      */
     public boolean isApplicableTo(IStructuredSelection selection,
             IObjectContributor contributor) {
         Iterator elements = selection.iterator();
         while (elements.hasNext()) {
             if (contributor.isApplicableTo(elements.next()) == false) {
                 return false;
             }
         }
         return true;
     }

     /**
      * Return whether the given contributor is applicable to all elements in the
      * list.
      *
      * @param list
      * the selection
      * @param contributor
      * the contributor
      * @return whether it is applicable
      */

     public boolean isApplicableTo(List list, IObjectContributor contributor) {
         Iterator elements = list.iterator();
         while (elements.hasNext()) {
             if (contributor.isApplicableTo(elements.next()) == false) {
                 return false;
             }
         }
         return true;
     }

     /**
      * Register a contributor.
      *
      * @param contributor the contributor
      * @param targetType the target type
      */
     public void registerContributor(IObjectContributor contributor,
             String targetType) {
         List contributorList = (List ) contributors.get(targetType);
         if (contributorList == null) {
             contributorList = new ArrayList (5);
             contributors.put(targetType, contributorList);
         }
         contributorList.add(contributor);
         flushLookup();

         IConfigurationElement element = (IConfigurationElement) Util.getAdapter(contributor,
             IConfigurationElement.class);
         
         //hook the object listener
 if (element != null) {
             ContributorRecord contributorRecord = new ContributorRecord(
                     contributor, targetType);
             contributorRecordSet.add(contributorRecord);
             PlatformUI.getWorkbench().getExtensionTracker().registerObject(
                     element.getDeclaringExtension(), contributorRecord,
                     IExtensionTracker.REF_WEAK);
         }
     }

     /**
      * Unregister all contributors.
      */
     public void unregisterAllContributors() {
         contributors = new Hashtable (5);
         flushLookup();
     }

     /**
      * Unregister a contributor from the target type.
      *
      * @param contributor the contributor
      * @param targetType the target type
      */
     public void unregisterContributor(IObjectContributor contributor,
             String targetType) {
         List contributorList = (List ) contributors.get(targetType);
         if (contributorList == null) {
             return;
         }
         contributorList.remove(contributor);
         if (contributorList.isEmpty()) {
             contributors.remove(targetType);
         }
         flushLookup();
     }


     /**
      * Unregister all contributors for the target type.
      *
      * @param targetType the target type
      */
     public void unregisterContributors(String targetType) {
         contributors.remove(targetType);
         flushLookup();
     }
     
     protected List getContributors(Object object) {
         // Determine is the object is a resource
 Object resource = LegacyResourceSupport.getAdaptedContributorResource(object);
         
         // Fetch the unique adapters
 List adapters = new ArrayList (Arrays.asList(Platform.getAdapterManager().computeAdapterTypes(object.getClass())));
         removeCommonAdapters(adapters, Arrays.asList(new Class [] {object.getClass()}));

         List contributors = new ArrayList ();
         
         // Calculate the contributors for this object class
 addAll(contributors, getObjectContributors(object.getClass()));
         // Calculate the contributors for resource classes
 if(resource != null) {
             addAll(contributors, getResourceContributors(resource.getClass()));
         }
         // Calculate the contributors for each adapter type
 if(adapters != null) {
             for (Iterator it = adapters.iterator(); it.hasNext();) {
                 String adapter = (String ) it.next();
                 addAll(contributors, getAdaptableContributors(adapter));
             }
         }
         
         // Remove duplicates. Note: this -must- maintain the element order to preserve menu order.
 contributors = removeDups(contributors);

         return contributors.isEmpty() ? Collections.EMPTY_LIST : new ArrayList (contributors);
     }
     
     /**
      * Returns the contributions for the given class. This considers
      * contributors on any super classes and interfaces.
      *
      * @param objectClass the class to search for contributions.
      * @return the contributions for the given class. This considers
      * contributors on any super classes and interfaces.
      *
      * @since 3.1
      */
     protected List getObjectContributors(Class objectClass) {
         List objectList = null;
         // Lookup the results in the cache first.
 if (objectLookup != null) {
             objectList = (List ) objectLookup.get(objectClass);
         }
         if (objectList == null) {
             objectList = addContributorsFor(objectClass);
             if (objectList.size() == 0) {
                 objectList = Collections.EMPTY_LIST;
             }
             else {
                 objectList = Collections.unmodifiableList(objectList);
             }
             cacheObjectLookup(objectClass, objectList);
         }
         return objectList;
     }

     /**
      * Returns the contributions for the given <code>IResource</code>class.
      * This considers contributors on any super classes and interfaces. This
      * will only return contributions that are adaptable.
      *
      * @param resourceClass the class to search for contributions.
      * @return the contributions for the given class. This considers
      * adaptable contributors on any super classes and interfaces.
      *
      * @since 3.1
      */
     protected List getResourceContributors(Class resourceClass) {
         List resourceList = null;
         if (resourceAdapterLookup != null) {
             resourceList = (List ) resourceAdapterLookup.get(resourceClass);
         }
         if (resourceList == null) {
             resourceList = addContributorsFor(resourceClass);
             if (resourceList.size() == 0) {
                 resourceList = Collections.EMPTY_LIST;
             } else {
                 resourceList = Collections.unmodifiableList(filterOnlyAdaptableContributors(resourceList));
             }
             cacheResourceAdapterLookup(resourceClass, resourceList);
         }
         return resourceList;
     }

     /**
      * Returns the contributions for the given type name.
      *
      * @param adapterType the class to search for contributions.
      * @return the contributions for the given class. This considers
      * contributors to this specific type.
      *
      * @since 3.1
      */
     protected List getAdaptableContributors(String adapterType) {
         List adaptableList = null;
         // Lookup the results in the cache first, there are two caches
 // one that stores non-adapter contributions and the other
 // contains adapter contributions.
 if (adaptableLookup != null) {
             adaptableList = (List ) adaptableLookup.get(adapterType);
         }
         if (adaptableList == null) {
             // ignore resource adapters because these must be adapted via the
 // IContributorResourceAdapter.
 if (LegacyResourceSupport.isResourceType(adapterType) || LegacyResourceSupport.isResourceMappingType(adapterType)) {
                 adaptableList = Collections.EMPTY_LIST;
             }
             else {
                 adaptableList = (List ) contributors.get(adapterType);
                 if (adaptableList == null || adaptableList.size() == 0) {
                     adaptableList = Collections.EMPTY_LIST;
                 } else {
                     adaptableList = Collections.unmodifiableList(filterOnlyAdaptableContributors(adaptableList));
                 }
             }
             cacheAdaptableLookup(adapterType, adaptableList);
         }
         return adaptableList;
     }
     
     /**
      * Prunes from the list of adapters type names that are in the class
      * search order of every class in <code>results</code>.
      * @param adapters
      * @param results
      * @since 3.1
      */
     protected void removeCommonAdapters(List adapters, List results) {
         for (Iterator it = results.iterator(); it.hasNext();) {
             Class clazz = ((Class ) it.next());
             List commonTypes = computeCombinedOrder(clazz);
             for (Iterator it2 = commonTypes.iterator(); it2.hasNext();) {
                 Class type = (Class ) it2.next();
                 adapters.remove(type.getName());
             }
         }
     }
     
     /**
      * Returns the class search order starting with <code>extensibleClass</code>.
      * The search order is defined in this class' comment.
      */
     protected List computeCombinedOrder(Class inputClass) {
         List result = new ArrayList (4);
         Class clazz = inputClass;
         while (clazz != null) {
             // add the class
 result.add(clazz);
             // add all the interfaces it implements
 Class [] interfaces = clazz.getInterfaces();
             for (int i = 0; i < interfaces.length; i++) {
                 result.add(interfaces[i]);
             }
             // get the superclass
 clazz = clazz.getSuperclass();
         }
         return result;
     }

     private List filterOnlyAdaptableContributors(List contributors) {
         List adaptableContributors = null;
         for (Iterator it = contributors.iterator(); it.hasNext();) {
             IObjectContributor c = (IObjectContributor) it.next();
             if(c.canAdapt()) {
                 if(adaptableContributors == null) {
                     adaptableContributors = new ArrayList ();
                 }
                 adaptableContributors.add(c);
             }
         }
         return adaptableContributors == null ? Collections.EMPTY_LIST : adaptableContributors;
     }
     
     /* (non-Javadoc)
      * @see org.eclipse.core.runtime.dynamicHelpers.IExtensionChangeHandler#removeExtension(org.eclipse.core.runtime.IExtension, java.lang.Object[])
      */
     public void removeExtension(IExtension source, Object [] objects) {
         for (int i = 0; i < objects.length; i++) {
             if (objects[i] instanceof ContributorRecord) {
                 ContributorRecord contributorRecord = (ContributorRecord) objects[i];
                 unregisterContributor((contributorRecord).contributor, (contributorRecord).objectClassName);
                 contributorRecordSet.remove(contributorRecord);
             }
         }
     }

     /**
      * Remove listeners and dispose of this manager.
      *
      * @since 3.1
      */
     public void dispose() {
         if(canHandleExtensionTracking()) {
             PlatformUI.getWorkbench().getExtensionTracker().unregisterHandler(this);
         }
     }
     
     /**
      * Returns the list of contributors that are interested in the
      * given list of model elements.
      * @param elements a list of model elements (<code>Object</code>)
      * @return the list of interested contributors (<code>IObjectContributor</code>)
      */
     protected List getContributors(List elements) {
         // Calculate the common class, interfaces, and adapters registered
 // via the IAdapterManager.
 List commonAdapters = new ArrayList ();
         List commonClasses = getCommonClasses(elements, commonAdapters);
         
         // Get the resource class. It will be null if any of the
 // elements are resources themselves or do not adapt to
 // IResource.
 Class resourceClass = getCommonResourceClass(elements);
         Class resourceMappingClass = getResourceMappingClass(elements);

         // Get the contributors.

         List contributors = new ArrayList ();
         
         // Add the resource contributions to avoid duplication
 if (resourceClass != null) {
             addAll(contributors, getResourceContributors(resourceClass));
         }
         if (commonClasses != null && !commonClasses.isEmpty()) {
             for (int i = 0; i < commonClasses.size(); i++) {
                 List results = getObjectContributors((Class ) commonClasses
                         .get(i));
                 addAll(contributors, results);
             }
         }
         // Add the resource mappings explicitly to avoid possible duplication
 if (resourceMappingClass == null) {
             // Still show the menus if the object is not adaptable but the adapter manager
 // has an entry for it
 resourceMappingClass = LegacyResourceSupport
                     .getResourceMappingClass();
             if (resourceMappingClass != null
                     && commonAdapters.contains(resourceMappingClass.getName())) {
                 addAll(contributors, getResourceContributors(resourceMappingClass));
             }
         } else {
             contributors.addAll(getResourceContributors(resourceMappingClass));
         }
         if (!commonAdapters.isEmpty()) {
             for (Iterator it = commonAdapters.iterator(); it.hasNext();) {
                 String adapter = (String ) it.next();
                 addAll(contributors, getAdaptableContributors(adapter));
             }
         }
         
         // Remove duplicates. Note: this -must- maintain the element order to preserve menu order.
 contributors = removeDups(contributors);
         
         return contributors.isEmpty() ? Collections.EMPTY_LIST : new ArrayList (contributors);
     }

     /**
      * Adds all items in toAdd to the given collection. Optimized to avoid creating an iterator.
      * This assumes that toAdd is efficient to index (i.e. it's an ArrayList or some other RandomAccessList),
      * which is the case for all uses in this class.
      */
     private static void addAll(Collection collection, List toAdd) {
         for (int i = 0, size = toAdd.size(); i < size; ++i) {
             collection.add(toAdd.get(i));
         }
     }

     /**
      * Removes duplicates from the given list, preserving order.
      */
     private static List removeDups(List list) {
         if (list.size() <= 1) {
             return list;
         }
         HashSet set = new HashSet (list);
         if (set.size() == list.size()) {
             return list;
         }
         ArrayList result = new ArrayList (set.size());
         for (Iterator i = list.iterator(); i.hasNext();) {
             Object o = i.next();
             if (set.remove(o)) {
                 result.add(o);
             }
         }
         return result;
     }
     
     /**
      * Returns the common denominator class, interfaces, and adapters
      * for the given collection of objects.
      */
     private List getCommonClasses(List objects, List commonAdapters) {
         if (objects == null || objects.size() == 0) {
             return null;
         }

         // Optimization: if n==1 (or if all objects are of the same class), then the common class is the object's class,
 // and the common adapters are the adapters cached for that class in the adapter manager
 // See bug 177592 for more details.
 if (allSameClass(objects)) {
             
             Class clazz = objects.get(0).getClass();
             commonAdapters.addAll(Arrays.asList(Platform.getAdapterManager().computeAdapterTypes(clazz)));
             List result = new ArrayList (1);
             result.add(clazz);
             return result;
         }
         
         // Compute all the super classes, interfaces, and adapters
 // for the first element.
 List classes = computeClassOrder(objects.get(0).getClass());
         List adapters = computeAdapterOrder(classes);
         List interfaces = computeInterfaceOrder(classes);

         // Cache of all types found in the selection - this is needed
 // to compute common adapters.
 List lastCommonTypes = new ArrayList ();

         boolean classesEmpty = classes.isEmpty();
         boolean interfacesEmpty = interfaces.isEmpty();

         // Traverse the selection if there is more than one element selected.
 for (int i = 1; i < objects.size(); i++) {
             // Compute all the super classes for the current element
 List otherClasses = computeClassOrder(objects.get(i).getClass());
             if (!classesEmpty) {
                 classesEmpty = extractCommonClasses(classes, otherClasses);
             }

             // Compute all the interfaces for the current element
 // and all of its super classes.
 List otherInterfaces = computeInterfaceOrder(otherClasses);
             if (!interfacesEmpty) {
                 interfacesEmpty = extractCommonClasses(interfaces,
                         otherInterfaces);
             }

             // Compute all the adapters provided for the calculated
 // classes and interfaces for this element.
 List classesAndInterfaces = new ArrayList (otherClasses);
             if (otherInterfaces != null) {
                 classesAndInterfaces.addAll(otherInterfaces);
             }
             List otherAdapters = computeAdapterOrder(classesAndInterfaces);

             // Compute common adapters
 // Note here that an adapter can match a class or interface, that is
 // that an element in the selection may not adapt to a type but instead
 // be of that type.
 // If the selected classes doesn't have adapters, keep
 // adapters that match the given classes types (classes and interfaces).
 if (otherAdapters.isEmpty() && !adapters.isEmpty()) {
                 removeNonCommonAdapters(adapters, classesAndInterfaces);
             } else {
                 if (adapters.isEmpty()) {
                     removeNonCommonAdapters(otherAdapters, lastCommonTypes);
                     if (!otherAdapters.isEmpty()) {
                         adapters.addAll(otherAdapters);
                     }
                 } else {
                     // Remove any adapters of the first element that
 // are not in the current element's adapter list.
 for (Iterator it = adapters.iterator(); it.hasNext();) {
                         String adapter = (String ) it.next();
                         if (!otherAdapters.contains(adapter)) {
                             it.remove();
                         }
                     }
                 }
             }

             // Remember the common search order up to now, this is
 // used to match adapters against common classes or interfaces.
 lastCommonTypes.clear();
             lastCommonTypes.addAll(classes);
             lastCommonTypes.addAll(interfaces);

             if (interfacesEmpty && classesEmpty && adapters.isEmpty()) {
                 // As soon as we detect nothing in common, just exit.
 return null;
             }
         }

         // Once the common classes, interfaces, and adapters are
 // calculated, let's prune the lists to remove duplicates.
 ArrayList results = new ArrayList (4);
         ArrayList superClasses = new ArrayList (4);
         if (!classesEmpty) {
             for (int j = 0; j < classes.size(); j++) {
                 if (classes.get(j) != null) {
                     superClasses.add(classes.get(j));
                 }
             }
             // Just keep the first super class
 if (!superClasses.isEmpty()) {
                 results.add(superClasses.get(0));
             }
         }

         if (!interfacesEmpty) {
             removeCommonInterfaces(superClasses, interfaces, results);
         }

         // Remove adapters already included as common classes
 if (!adapters.isEmpty()) {
             removeCommonAdapters(adapters, results);
             commonAdapters.addAll(adapters);
         }
         return results;
     }

     /**
      * Returns <code>true</code> if all objects in the given list are of the same class,
      * <code>false</code> otherwise.
      */
     private boolean allSameClass(List objects) {
         int size = objects.size();
         if (size <= 1) return true;
         Class clazz = objects.get(0).getClass();
         for (int i = 1; i < size; ++i) {
             if (!objects.get(i).getClass().equals(clazz)) {
                 return false;
             }
         }
         return true;
     }

     private boolean extractCommonClasses(List classes, List otherClasses) {
         boolean classesEmpty = true;
         if (otherClasses.isEmpty()) {
             // When no super classes, then it is obvious there
 // are no common super classes with the first element
 // so clear its list.
 classes.clear();
         } else {
             // Remove any super classes of the first element that
 // are not in the current element's super classes list.
 for (int j = 0; j < classes.size(); j++) {
                 if (classes.get(j) != null) {
                     classesEmpty = false; // TODO: should this only be set if item not nulled out?
 if (!otherClasses.contains(classes.get(j))) {
                         classes.set(j, null);
                     }
                 }
             }
         }
         return classesEmpty;
     }

     private void removeNonCommonAdapters(List adapters, List classes) {
         for (int i = 0; i < classes.size(); i++) {
             Object o = classes.get(i);
             if (o != null) {
                 Class clazz = (Class ) o;
                 String name = clazz.getName();
                 if (adapters.contains(name)) {
                     return;
                 }
             }
         }
         adapters.clear();
     }

     private void removeCommonInterfaces(List superClasses, List types,
             List results) {
         List dropInterfaces = null;
         if (!superClasses.isEmpty()) {
             dropInterfaces = computeInterfaceOrder(superClasses);
         }
         for (int j = 0; j < types.size(); j++) {
             if (types.get(j) != null) {
                 if (dropInterfaces != null
                         && !dropInterfaces.contains(types.get(j))) {
                     results.add(types.get(j));
                 }
             }
         }
     }

     private List computeAdapterOrder(List classList) {
         Set result = new HashSet (4);
         IAdapterManager adapterMgr = Platform.getAdapterManager();
         for (Iterator list = classList.iterator(); list.hasNext();) {
             Class clazz = ((Class ) list.next());
             String [] adapters = adapterMgr.computeAdapterTypes(clazz);
             for (int i = 0; i < adapters.length; i++) {
                 String adapter = adapters[i];
                 if (!result.contains(adapter)) {
                     result.add(adapter);
                 }
             }
         }
         return new ArrayList (result);
     }

     /**
      * Returns the common denominator resource class for the given
      * collection of objects.
      * Do not return a resource class if the objects are resources
      * themselves so as to prevent double registration of actions.
      */
     private Class getCommonResourceClass(List objects) {
         if (objects == null || objects.size() == 0) {
             return null;
         }
         Class resourceClass = LegacyResourceSupport.getResourceClass();
         if (resourceClass == null) {
             // resources plug-in not loaded - no resources. period.
 return null;
         }

         List testList = new ArrayList (objects.size());

         for (int i = 0; i < objects.size(); i++) {
             Object object = objects.get(i);

             if (object instanceof IAdaptable) {
                 if (resourceClass.isInstance(object)) {
                     continue;
                 }

                 Object resource = LegacyResourceSupport
                         .getAdaptedContributorResource(object);

                 if (resource == null) {
                     //Not a resource and does not adapt. No common resource class
 return null;
                 }
                 testList.add(resource);
             } else {
                 return null;
             }
         }

         return getCommonClass(testList);
     }

     /**
      * Return the ResourceMapping class if the elements all adapt to it.
      */
     private Class getResourceMappingClass(List objects) {
         if (objects == null || objects.size() == 0) {
             return null;
         }
         Class resourceMappingClass = LegacyResourceSupport
                 .getResourceMappingClass();
         if (resourceMappingClass == null) {
             // resources plug-in not loaded - no resources. period.
 return null;
         }

         for (int i = 0; i < objects.size(); i++) {
             Object object = objects.get(i);

             if (object instanceof IAdaptable) {
                 if (resourceMappingClass.isInstance(object)) {
                     continue;
                 }

                 Object resourceMapping = LegacyResourceSupport
                         .getAdaptedContributorResourceMapping(object);

                 if (resourceMapping == null) {
                     //Not a resource and does not adapt. No common resource class
 return null;
                 }
             } else {
                 return null;
             }
         }
         // If we get here then all objects adapt to ResourceMapping
 return resourceMappingClass;
     }

     /**
      * Returns the common denominator class for the given
      * collection of objects.
      */
     private Class getCommonClass(List objects) {
         if (objects == null || objects.size() == 0) {
             return null;
         }
         Class commonClass = objects.get(0).getClass();
         // try easy
 if (objects.size() == 1) {
             return commonClass;
         // try harder
 }

         for (int i = 1; i < objects.size(); i++) {
             Object object = objects.get(i);
             Class newClass = object.getClass();
             // try the short cut
 if (newClass.equals(commonClass)) {
                 continue;
             }
             // compute common class
 commonClass = getCommonClass(commonClass, newClass);
             // give up
 if (commonClass == null) {
                 return null;
             }
         }
         return commonClass;
     }

     /**
      * Returns the common denominator class for
      * two input classes.
      */
     private Class getCommonClass(Class class1, Class class2) {
         List list1 = computeCombinedOrder(class1);
         List list2 = computeCombinedOrder(class2);
         for (int i = 0; i < list1.size(); i++) {
             for (int j = 0; j < list2.size(); j++) {
                 Class candidate1 = (Class ) list1.get(i);
                 Class candidate2 = (Class ) list2.get(j);
                 if (candidate1.equals(candidate2)) {
                     return candidate1;
                 }
             }
         }
         // no common class
 return null;
     }
 }

